<?php

declare(strict_types=1);

namespace Tools\Command;

class CommandActuator
{
    /**
     * Program
     *
     * @var string
     */
    private string $program;

    /**
     * Stream resource config
     *
     * @var array
     */
    private array $descriptorSpecial;

    /**
     * Stream resource
     *
     * @var array
     */
    private array $pipes;

    /**
     * Execute path
     *
     * @var string
     */
    private string $cwd;

    /**
     * Env
     *
     * @var array
     */
    private array $env;

    /**
     * More parameter
     *
     * @var array
     */
    private array $otherOptions;

    /**
     * Resouce flag
     *
     * @var resource
     */
    private $process;

    /**
     * Process ID
     *
     * @var int|string
     */
    public $pid;

    /**
     * Command execute status
     *
     * @var string
     */
    public $returnValue;

    /**
     * Command execute result
     *
     * @var string
     */
    public string $result;

    /**
     * Constructor
     *
     * @param string $program
     * @param array $descriptorSpecial
     * @param string|null $cwd
     * @param array $env
     * @param array $data
     */
    public function __construct(string $program, array $descriptorSpecial = [], string $cwd = null, array $env = [], array $data = [])
    {
        $this->program = $program;
        if (empty($descriptorSpecial)) {
            $this->descriptorSpecial = [
                ['pipe', 'r'],
                ['pipe', 'w'],
                // ['file', './channel.log', 'a']
            ];
        } else {
            $this->descriptorSpecial = $descriptorSpecial;
        }
        $this->cwd = $cwd;
        $this->env = $env;
        $this->otherOptions = $data;

        $this->process = null;

        $this->pipes = [];

        $this->returnValue = 0;
        $this->result = '';
    }

    /**
     * Open resource
     *
     * @return bool
     */
    public function open()
    {
        if ($this->status()) return true;

        $this->process = proc_open($this->program, $this->descriptorSpecial, $this->pipes, $this->cwd, $this->env, $this->otherOptions);

        if (is_resource($this->process)) {
            $information = proc_get_status($this->process);
            if ($information === false) return false;

            $this->pid = $information['pid'];

            return true;
        } else {
            $this->process = null;
        }

        return false;
    }

    /**
     * Run command
     *
     * @param array $commandArray
     * @return bool
     */
    public function runCommand(array $commandArray)
    {
        if (!$this->open()) return false;

        // in
        foreach ($commandArray as $command) {
            fwrite($this->pipes[0], $command);
        }
        fclose($this->pipes[0]);

        // out
        $this->result .= stream_get_contents($this->pipes[1]);
        fclose($this->pipes[1]);

        return $this->close();
    }

    /**
     * Modify the current process priority.
     *
     * @param integer $increment
     * @return bool
     */
    public static function nice(int $increment) 
    {
        return proc_nice($increment);
    }

    /**
     * Kill the process for command open.
     *
     * @param integer $signal SIGTERM
     * @return bool
     */
    public function terminate(int $signal = 15)
    {
        if ($this->status()) {
            return proc_terminate($this->process, $signal);
        }

        return true;
    }

    /**
     * Status
     *
     * @return bool
     */
    public function status()
    {
        if (is_null($this->process)) return false;

        $information = proc_get_status($this->process);

        if ($information === false) return false;

        return $information['running'];
    }

    /**
     * Close
     *
     * @return bool
     */
    public function close()
    {
        if (is_null($this->process)) return true;

        $this->returnValue = proc_close($this->process);
        $this->process = null;

        return true;
    }
}